function expovariate(taxa) {
	// Utilizamos a técnica da inversa da função cumulativa.
	// Obs: Math.log já é na base neperiana.
	var u;
	do {
		u = Math.random();
	} while(u == 0);
	var x = -1.0/taxa * Math.log(u);
	if (x <= 0) {
		throw "assert failed (x > 0)";
	}
	return x;
}

function User(simulador, nome) {
	this.ativo = true;
	this.nome = nome;
	this.simulador = simulador;

	// Profile
	this.chegada = 0;
	this.saida = 0;
	return this;
}
User.prototype = {
	toString: function() {
		return this.nome + " " + this.chegada;	
	}
}
	
function Simulador(settings) {
	this.settings = settings;
	this.proxima_amostragem = 2;
	
	this.nr_usuarios = [];
	this.tempo = [];
	this.nr_amostras = 0;
	this.nr_usuarios_total = 0;

	// Usuários que estão ativos no sistema.
	this.users_ativos = [];

	this.users_inativos = [];

	// Tempo da simulação.
	this.tempo_atual = 0;

	// Fila de eventos.
	this.fila_eventos = [];

	// Utilizado para cancelar a execução de uma simulação.
	// O estado do sistema se mantém consistente e sua execução
	// pode ser continuada apenas chamando run() novamente.
	this.cancelar = false;

	// Indica que a simulação foi concluída.
	this.concluido = false;

	return this;
}
Simulador.prototype = {
	add_sample: function () {
		var nr_usuarios = this.users_ativos.length;

		this.nr_usuarios.push(nr_usuarios);
		this.tempo.push(this.proxima_amostragem);
		this.proxima_amostragem += 2;
		this.nr_amostras += 1;
		this.nr_usuarios_total += nr_usuarios;
	},
	add_user: function(nome) {
		// Instancia o user.
		var user = new User(this, nome);

		// Profile.
		user.chegada = this.tempo_atual;

		// Adiciona o User aos users ativos do simulador.
		this.users_ativos.push(user);
		this.users_ativos.sort(function(a, b) {
			return b.chegada - a.chegada;
		});

		add_pessoa_na_fila(user.nome);
		return user
	},
	add_evento: function(dt, evento) {
		this.fila_eventos.push({ tempo: this.tempo_atual + dt, evento: evento });
		this.fila_eventos.sort(function(a, b) {
			return b.tempo - a.tempo;
		});
	},
	next_evento: function() {
		return this.fila_eventos.pop();
	},
	prepare: function() {
		self.tempo_atual = 0;
			 
		// inicializa população inicial
		for(var i=0; i < this.settings.n0; i++) {
			var nome = i;
			var user = this.add_user(nome);
		}
		var dt = expovariate(this.settings.lambda);
		this.add_evento(dt, new EventoUserChegada(this));

		var dt = expovariate(this.settings.mu);
		this.add_evento(dt, new EventoUserSaida(this));

		//this.add_evento(this.settings.dt_amostragem, new EventoUserProfile(this));
	},
	run: function() {
		$("#play").attr("disabled", true);

		var item;
		var self = this;

		item = self.next_evento();
		atualiza_evento(item);
		atualiza_fila_eventos(self.fila_eventos);

		// Atualiza o tempo atual.
		self.tempo_atual = item.tempo;

		if (self.tempo_atual > self.proxima_amostragem) {
			var amostragem = new EventoUserProfile(this);
			amostragem.run();
		}

		// Executa o evento.
		setTimeout(function () {
			item.evento.run();
			$("#play").attr("disabled", false);
			atualiza_fila_eventos(self.fila_eventos);
			if (self.play && (! self.pause)) {
				setTimeout(function() { self.run()}, 300);
			}
		}, 300);
	}
}

